import _ from 'lodash';
import Router from 'koa-router';
import assert from 'assert';
import Group from '../models/group';
import ReplyTemplateGroup from '../models/replyTemplateGroup';
import User from '../models/user';
import Channel from '../models/channel';
import tokenAuth from '../middleware/tokenAuth';
import waitHandler from '../handler/waitHandler';
import channelHandlers from '../handler/channelHandlers';

const router = new Router({
  prefix: '/api/groups',
});

router.use(tokenAuth());

router.post('/create', async (ctx) => {
  /**
   * validate inputs
   */
  const input = ctx.request.body;

  if (!_.has(input, 'name')) {
    ctx.throw('no_name');
  }
  const name = input.name.trim();
  const namePattern = new RegExp('^([\u4E00-\uFA29]|[\uE7C7-\uE7F3]|[a-zA-Z0-9_-]){2,30}$');
  if (!namePattern.test(name)) {
    ctx.throw('invalid_name');
  }

  let group = await Group.findOne({ name });
  if (group) {
    ctx.throw('group_exists');
  }

  group = new Group({
    name,
  });
  await group.save();

  ctx.body = {
    ok: true,
    group,
  };
});

router.post('/list', async (ctx) => {
  const groups = await Group.find();
  ctx.body = {
    ok: true,
    groups: groups.map(g => g.get('name')),
  };
});

router.post('/setIsCustomer', async (ctx) => {
  const input = ctx.request.body;
  if (!_.has(input, 'name')) {
    ctx.throw('no_name');
  }
  const group = await Group.findOne({ name: input.name });
  if (group === null) {
    ctx.throw('no_group');
  }

  group.isCustomer = Boolean(input.isCustomer);

  const res = await group.save();
  if (res) {
    ctx.body = {
      ok: true,
      group,
    };
  } else {
    ctx.throw('unknown_error');
  }
});

router.post('/addResponse', async (ctx) => {
  const input = ctx.request.body;
  if (_.isNull(input.name)) {
    ctx.throw('no_name');
  }
  const group = await Group.findOne({ name: input.name });
  if (group === null) {
    ctx.throw('no_group');
  }

  if (_.isNull(input.response_group)) {
    ctx.throw('no_response_name');
  }
  const responseGroup = await Group.findOne({ name: input.response_group });
  if (responseGroup === null) {
    ctx.throw('no_response_group');
  }

  group.responseGroups.addToSet(responseGroup);
  await group.save();

  ctx.body = {
    ok: true,
    group,
  };
});

router.post('/removeResponse', async (ctx) => {
  const input = ctx.request.body;
  if (_.isNull(input.name)) {
    ctx.throw('no_name');
  }
  const group = await Group.findOne({ name: input.name });
  if (group === null) {
    ctx.throw('no_group');
  }

  if (_.isNull(input.response_group)) {
    ctx.throw('no_response_name');
  }
  const responseGroup = await Group.findOne({ name: input.response_group });
  if (responseGroup === null) {
    ctx.throw('no_response_group');
  }

  group.responseGroups.remove(responseGroup);
  await group.save();

  ctx.body = {
    ok: true,
    group,
  };
});

router.post('/addRobot', async (ctx) => {
  const input = ctx.request.body;
  if (_.isNull(input.name)) {
    ctx.throw('no_name');
  }
  const group = await Group.findOne({ name: input.name });
  if (group === null) {
    ctx.throw('no_group');
  }

  if (_.isNull(input.robot)) {
    ctx.throw('no_robot_name');
  }
  const robot = await User.findOne({ username: input.robot });
  if (robot === null) {
    ctx.throw('robot_not_found');
  }

  group.robots.addToSet(robot);
  await group.save();

  // find group's channel
  const channels = await Channel.find({ group });
  // join robot to channel
  for (const channel of channels) {
    channelHandlers.join(channel, robot, ctx.redis);
  }

  ctx.body = {
    ok: true,
    group,
  };
});

router.post('/users/list', async (ctx) => {
  const input = ctx.request.body;
  if (!_.has(input, 'name')) {
    ctx.throw('no_name');
  }
  const group = await Group.findOne({ name: input.name });
  if (group === null) {
    ctx.throw('no_group');
  }

  const members = await User.find({ group });

  ctx.body = {
    ok: true,
    members: members.map(m => m.id),
  };
});

router.post('/users/add', async (ctx) => {
  const input = ctx.request.body;
  if (!_.has(input, 'name')) {
    ctx.throw('no_name');
  }
  const group = await Group.findOne({ name: input.name });
  if (group === null) {
    ctx.throw('no_group');
  }

  if (_.isNull(input.user_id)) {
    ctx.throw('no_user_id');
  }
  const user = await User.findById(input.user_id);
  if (user === null) {
    ctx.throw('no_user');
  }

  // group.update({$push: {members: user}});
  user.group = group;
  user.save();

  ctx.body = {
    ok: true,
    group,
  };
});

router.post('/users/remove', async (ctx) => {
  const input = ctx.request.body;
  if (!_.has(input, 'name')) {
    ctx.throw('no_name');
  }
  const group = await Group.findOne({ name: input.name });
  if (group === null) {
    ctx.throw('no_group');
  }

  if (_.isNull(input.user_id)) {
    ctx.throw('no_user_id');
  }
  const user = await User.findById(input.user_id).populate('group');
  if (user === null) {
    ctx.throw('no_user');
  }

  if (user.group.id !== group.id) {
    ctx.throw('no_user_in_group');
  }

  user.group = null;
  user.save();

  ctx.body = {
    ok: true,
    group,
  };
});

router.post('/onlineStatus', async (ctx) => {
  const groupName = ctx.request.body.group;

  assert(/助教|教务/.test(groupName), 'group_name_invalid');

  const redisKeyMaps = {
    助教: 'student:waitList:threshold',
    教务: 'teacher:waitList:threshold',
  };
  const threshold = await ctx.redis.get(redisKeyMaps[groupName]);
  const group = await Group.findOne({ name: groupName });
  const members = await User.find({ group });

  const memberJiuquIds = members.map(m => m.jiuquId);

  let result = {};

  if (memberJiuquIds.length) {
    const hmgetArgs = ['user:online', ...memberJiuquIds];
    const onlineStatus = await ctx.redis.hmget(...hmgetArgs);
    result = _.zipObject(memberJiuquIds, onlineStatus);
  }

  ctx.body = {
    ok: true,
    result,
    threshold: threshold ? Number(threshold) : 0,
  };
});

router.post('/updateWaitListThreshold', async (ctx) => {
  const { threshold, group } = ctx.request.body;
  const MIN_THRESHOLD = 10;
  const redisKeyMaps = {
    学生: 'student:waitList:threshold',
    老师: 'teacher:waitList:threshold',
  };
  assert(Number.isInteger(threshold) && (threshold === 0 || threshold >= MIN_THRESHOLD), 'invalid_threshold');
  assert(/学生|老师/.test(group), 'group_name_invalid');
  const redisKey = redisKeyMaps[group];

  assert(redisKey, 'redis_key_invalid');
  await ctx.redis.set(redisKey, threshold);

  ctx.body = {
    ok: true,
    threshold,
  };
});

router.post('/waitList', async (ctx) => {
  const page = ctx.request.body.page || 1;
  const pageSize = ctx.request.body.pageSize || 5;
  const prefer = ctx.request.body.prefer || {};

  const customerGroup = await Group.findOne({ name: ctx.request.body.group });
  const result = await waitHandler.list(ctx.viewer, customerGroup, page, pageSize, prefer);
  ctx.body = {
    ok: true,
    total: result.total,
    page,
    pageSize,
    channels: result.channels,
  };
});

router.post('/replyTemplates', async (ctx) => {
  const group = await Group.findOne({ name: ctx.request.body.group });
  const replyTemplateGroups = await ReplyTemplateGroup.find({
    user_group: group,
  }).populate('templates');

  ctx.body = {
    ok: true,
    group_id: group._id,
    reply_template_groups: replyTemplateGroups.map(g => ({
      id: g._id.toString(),
      name: g.name,
      templates: g.templates.map(template => ({
        id: template._id.toString(),
        content: template.content,
        template_group: template.template_group,
      })),
    })),
  };
});

export default router;
